package com.github.bobjoy.java.desginpattern;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;

/**
 * 单例模式
 * 1. 经典
 * 2. 懒加载
 * 3. 双重检查锁定
 * 4. 静态内部类
 * 5. 枚举
 *
 * https://blog.csdn.net/goodlixueyong/article/details/51935526
 * https://www.toutiao.com/i6546395291954708999/
 */
public class SingletonDemo {

    public static void main(String[] args) throws Exception {
        int threadTotal = 5000;
        Set<Object> set = new HashSet<>();
        CountDownLatch cdl = new CountDownLatch(threadTotal);
        long start = System.currentTimeMillis();
        for (int i=0; i < threadTotal; i++) {
            new Thread(() -> {
                try {

//                    set.add(Singleton1.getInstance());
//                    set.add(Singleton2.getInstance());
//                    set.add(Singleton3.getInstance());
//                    set.add(Singleton4.getInstance());
                    set.add(Singleton5.getInstance());
                } catch (Exception e) {
                    e.printStackTrace();
                }
                cdl.countDown();
            }).start();
        }
        cdl.await();
        System.out.println(System.currentTimeMillis() - start);

        for (Object obj : set) {
            System.out.println(obj);
        }

        set.clear();
    }

}

/**
 * 1. 经典（饿汉模式）
 *
 * - 入门级的单例写法像下面这样，这种方式的弊端明显，对象在类被加载的时候就实例化，对于消耗资源的类型来说不适用这种方式，像文件系统/数据库。
 * - 同时如果在使用到反射来实例化对象的场景下，这种写法也是线程不安全的，它避免不了生成多个实例。
 */
class Singleton1 {
    private static Singleton1 instance = new Singleton1();
    private Singleton1() {}
    public static Singleton1 getInstance() {
        return instance;
    }
}

/**
 * 2. 懒加载（懒汉模式）
 *
 * - 可能80%的开发会写这样的单例代码
 * - 懒加载这种写法，在单线程情况下没问题，但是如果出现多线程的情况，那么单例就会失效。
 *    对于多线程的情况，很自然的我们会想直接给 getInstance()方法加个同步块就可以解决，
 *    但是在90%的情况下是不需要同步的，只有在第一次实例化的时候才需要。因此衍生了双重检查锁定。
 */
class Singleton2 {
    private static Singleton2 instance = null;
    private Singleton2() {}
    public static Singleton2 getInstance() {
        if (instance == null) {
            instance = new Singleton2();
        }
        return instance;
    }
}

/**
 * 3. 双重检查锁定
 *
 * - 在并发场景下，双重检查锁定既能避免多余的同步开销，也能避免不同线程重复实例化的问题
 * - 然而想破坏单例也是可能的，如果你足够了解JVM的话，会发现上面的写法可能会导致实例化的时候机器码被重排序，
 *   导致第二个线程有机会获得null的实例，从而再次实例化。
 * - 对于这种问题，需要给变量增加 volatile 关键字。
 */
class Singleton3 {
    private static volatile Singleton3 instance = null;
    private Singleton3() {}
    public static Singleton3 getInstance() {
        if (instance == null) {
            synchronized (Singleton3.class) {
                if (instance == null) {
                    instance = new Singleton3();
                }
            }
        }
        return instance;
    }
}

/**
 * 4. 静态内部类
 *
 * - 为了解决JVM内存模型带来的单例失效问题，Bill Pugh提出了用静态内部类实现单例的方式
 * - 这种写法的优势是，它既提供了懒加载的特性，又避免了使用同步块的开销。在Singleton类被加载的时候，
 *   内部静态类直到 getInstance()被调用前都不会加载，而静态类被加载后只会实例化一次单例。
 * - 如果要破坏这种单例，可以用反射的方法。其实很多框架中都会用反射，比如Spring，所以还是存在单例被破坏的情况。
 */
class Singleton4 {
    private Singleton4() {}
    private static class SingletonHolder {
        private final static Singleton4 INSTANCE = new Singleton4();
        private static Singleton4 getInstance() {
            return SingletonHolder.INSTANCE;
        }
    }
    public static Singleton4 getInstance() {
        return SingletonHolder.getInstance();
    }
}

/**
 * 5. 枚举
 *
 * 枚举单例其实是利用了Java的特性，在Java中，任何的枚举都只会被实例化一次，
 * 虽然这样保证了绝对的单例，但是失去了懒加载的特性。所以在部分需要考虑资源消耗而使用懒加载的场景下，就不适合用枚举单例了。
 */
class Singleton5 {
    private Singleton5() {}
    enum Singleton{
        INSTANCE;
        private Singleton5 instance ;
        Singleton() {
            instance = new Singleton5();
        }
        public Singleton5 getInstance() {
            return this.instance;
        }
    }
    public static Singleton5 getInstance() {
        return Singleton.INSTANCE.getInstance();
    }
}
